
#include "Common.h"
#include "IoService.h"
#include "Session.h"
#include "Server.h"
#include "Buffer.h"
#include "HttpRequestHeader.h"
#include "HttpResponseHeader.h"

namespace fx {

    namespace state {
        enum SessionState : boost::uint32_t {
            SESS_NULL,
            SESS_MATCHING,
            SESS_MATCHED,
        };
        enum HeadParseState : boost::uint32_t {
            HEAD_NULL,
            // <>
            HEAD_LT,
            // \r\n\r\n
            HEAD_EMPTY,
            HEAD_CR,
            HEAD_CRLF,
            HEAD_CRLF_CR,
        };
    }

    Session::Session(ServerPtr server)
        : socket_(IoService::IOS())
        , head_delim_("\r\n\r\n")
        , server_(server)
    {
    }

    SessionPtr Session::Create(ServerPtr server)
    {
        return SessionPtr(new Session(server));
    }

    void Session::Start()
    {
        if (true == is_running_) {
            return ;
        }
        is_running_ = true;

        session_state_ = state::SESS_NULL;

        std::cout << "-- Connection Start\n";

        // read head
        request_head_state_ = state::HEAD_NULL;
        PrepareRequestHeadBuffer();
    }

    void Session::Stop()
    {
        if (false == is_running_) {
            return ;
        }

        std::cout << "-- Connection Stop\n";

        boost::system::error_code error;
        socket_.close(error);

        is_running_ = false;
    }

    void Session::RequestHeadHandler()
    {
        if (false == is_running_) {
            return ;
        }
        // parse head
        std::istream iss(&request_head_buf_);
        if (iss.peek() == '<') {
            Buffer buf(request_head_buf_.size());
            iss.read((char *)buf.Data(), buf.Length());
            std::cout << "Request: " << buf.AsString() << std::endl;
            if (boost::algorithm::istarts_with(buf.AsString(), "<policy-file-request/>")) {
                std::ostream oss(&response_buf_);
                std::string crossdomain =
                    "<?xml version=\"1.0\" ?>\n"
                    "<cross-domain-policy>\n"
                    "  <allow-access-from domain=\"*\" to-ports=\"*\" />\n"
                    "</cross-domain-policy>";
                oss << crossdomain;
                oss.flush();
                AsyncWriteResponse();
                std::cout << "Response: " << crossdomain << "\n";
            }
            // disconnect
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            return ;
        }
        HttpRequestHeaderPtr request = HttpRequestHeader::Create(iss);
        if (!request) {
            // disconnect
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
        }
        else if (request->CountHeader("Content-Length") > 1) {
            // disconnect
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
        }
        else if (request->CountHeader("Content-Length") > 0) {
            boost::uint32_t length = 0;
            if (!request->GetHeaderAs("Content-Length", length)) {
                // disconnect
                IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            }
            else {
                // check content length
                if (length > 32 * 1024) {
                    std::cout << "content too large.." << std::endl;
                    IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
                }
                else if (length <= request_buf_.size()) {
                    std::cout << "length < request_buf " << length << "/" << request_buf_.size() << std::endl;
                    RequestContentHandler(request, boost::system::error_code(), 0);
                }
                else {
                    // receive content
                    boost::uint32_t trans_length = length - request_buf_.size();
                    boost::asio::async_read(socket_, request_buf_, 
                        boost::asio::transfer_at_least(trans_length), 
                        boost::bind(&Session::RequestContentHandler, shared_from_this(), request, _1, _2));
                }
            }
        }
        else {
            // process request
            RequestContentHandler(request, boost::system::error_code(), 0);
        }
    }

    void Session::RequestContentHandler(HttpRequestHeaderPtr request, const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if (false == is_running_) {
            return ;
        }
        if (error) {
            // disconnect
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            std::cout << __FUNCTION__ << ":" << __LINE__ << " " 
                << error.message() << std::endl;
            return ;
        }

        // read buffer
        Buffer request_content;
        boost::uint32_t content_length = 0;
        if (request->GetHeaderAs("Content-Length", content_length)) {
            request_content.Resize(content_length);
            if (request_content) {
                std::istream iss (&request_buf_);
                iss.read((char*)request_content.Data(), content_length);
            }
        }

        IoService::Post(boost::bind(&Session::ProcessRequest, shared_from_this(), request, request_content));

        PrepareRequestHeadBuffer();
    }

    void Session::AsyncReadHeader()
    {
        if (false == is_running_) {
            return ;
        }
        // read more
        boost::asio::async_read_until(socket_, request_buf_, 
            boost::regex("(\r\n\r\n)|(<policy-file-request/>)"),
            boost::bind(&Session::PrepareRequestHeadBufferHandler, shared_from_this(), _1, _2));
    }
    void Session::PrepareRequestHeadBufferHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if (false == is_running_) {
            return ;
        }
        if (!error) {
            PrepareRequestHeadBuffer();
        }
        else {
            // disconnect
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            std::cout << __FUNCTION__ << ":" << __LINE__ << " " 
                << error.message() << std::endl;
            return ;
        }
    }
    void Session::PrepareRequestHeadBuffer()
    {
        if (false == is_running_) {
            return ;
        }
        std::istream iss(&request_buf_);
        std::ostream oss(&request_head_buf_);
        int c = EOF;
        while ((c = iss.get()) != EOF)
        {
            oss.put(c);
            switch (request_head_state_) 
            {
            case state::HEAD_NULL:
                {
                    if (c == '<') {
                        request_head_state_ = state::HEAD_LT;
                    }
                    else if (c == '\r') {
                        request_head_state_ = state::HEAD_CR;
                    }
                    else {
                        request_head_state_ = state::HEAD_EMPTY;
                    }
                    break;
                }
            case state::HEAD_LT:
                {
                    if (c == '>') {
                        // ok
                        request_head_state_ = state::HEAD_NULL;
                        // notice
                        IoService::Post(boost::bind(&Session::RequestHeadHandler, shared_from_this()));
                        return ;
                    }
                    break;
                }
            case state::HEAD_EMPTY:
                {
                    if (c == '\r') {
                        request_head_state_ = state::HEAD_CR;
                    }
                    break;
                }
            case state::HEAD_CR:
                {
                    if (c == '\n') {
                        request_head_state_ = state::HEAD_CRLF;
                    }
                    else {
                        request_head_state_ = state::HEAD_EMPTY;
                    }
                    break;
                }
            case state::HEAD_CRLF:
                {
                    if (c == '\r') {
                        request_head_state_ = state::HEAD_CRLF_CR;
                    }
                    else {
                        request_head_state_ = state::HEAD_EMPTY;
                    }
                    break;
                }
            case state::HEAD_CRLF_CR:
                {
                    if (c == '\n') {
                        request_head_state_ = state::HEAD_NULL;
                        // notice
                        // notice
                        IoService::Post(boost::bind(&Session::RequestHeadHandler, shared_from_this()));
                        return ;
                    }
                    else {
                        request_head_state_ = state::HEAD_EMPTY;
                    }
                }
            default:
                {
                    std::cout << "Error State: " << request_head_state_ << std::endl;
                    IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
                    return ;
                }
            }
        }
        // continue read
        AsyncReadHeader();
    }
    void Session::AsyncWriteResponse()
    {
        boost::asio::async_write(socket_, response_buf_, 
            boost::bind(&Session::ResponseWriteHandler, shared_from_this(), _1, _2));
    }

    void Session::ResponseWriteHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
    {
        if (!error) {
            if (response_buf_.size() > 0) {
                AsyncWriteResponse();
            }
        }
        else {
            std::cout << __FUNCTION__ << ":" << __LINE__ << " " 
                << error.message() << std::endl;
        }
    }

    void Session::OnMessage(HttpHeaderPtr header, Buffer content)
    {
        bool need_write = (response_buf_.size() == 0);
        std::string content_length = boost::lexical_cast<std::string>(content.Length());
        header->SetHeader("Content-Length", content_length);
        std::ostream oss(&response_buf_);
        oss << header->ToString();
        if (content) {
            oss.write((const char*)content.Data(), content.Length());
        }
        oss.flush();
        // log
        std::cout << "<<\n" << header->ToString() 
            // << content.AsString() 
            << "--\n";
        // async write
        if (need_write) {
            AsyncWriteResponse();
        }
    }

    void Session::ProcessRequest(HttpRequestHeaderPtr request, Buffer request_content)
    {
        if (false == is_running_) {
            return ;
        }

        std::cout << ">>\n" << request->ToString() 
            //<< request_content.AsString() 
            << "--\n";

        // method
        std::string method = request->GetMethod();

        // crossdomain
        if (request->GetMethod() == "GET" && request->GetPath() == "/crossdomain.xml") {
            Buffer response_content;
            HttpResponseHeaderPtr response_header = HttpResponseHeader::Create();
            // field
            response_header->SetProtocol("HTTP");
            response_header->SetVersion(request->GetVersion());
            response_header->SetCode("200");
            response_header->SetDescription("OK");
            std::string crossdomain =
                "<?xml version=\"1.0\" ?>\n"
                "<cross-domain-policy>\n"
                "  <allow-access-from domain=\"*\" />\n"
                "</cross-domain-policy>";
            response_content = Buffer(crossdomain);
            std::string response_content_length = boost::lexical_cast<std::string>(response_content.Length());
            response_header->SetHeader("Content-Length", response_content_length);
            response_header->SetHeader("Content-Type", "text/xml");
            // send message
            OnMessage(response_header, response_content);
        }
        else if (request->GetMethod() == "EVENT") {
            // process
            std::string request_path = request->GetPath();
            if (session_state_ == state::SESS_NULL && request_path == "/connect") {
            }
            else if (request_path == "/disconnect") {
            }
            else if (session_state_ == state::SESS_MATCHED && request_path == "/message") {
            }
            else {
                // error
                IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            }
        }
        else if (request->GetMethod() == "INFO") {
            if (session_state_ == state::SESS_MATCHED) {
            }
            else {
                // error
                IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
            }
        }
        else {
            // error
            IoService::Post(boost::bind(&Session::Stop, shared_from_this()));
        }
    }
    void Session::OnMatching()
    {
        if (false == is_running_) {
            return ;
        }
        session_state_ = state::SESS_MATCHING;
        //
        HttpRequestHeaderPtr header = HttpRequestHeader::Create();
        header->SetMethod("EVENT");
        header->SetPath("/status");
        header->SetProtocol("XW");
        header->SetVersion("1.0");
        header->SetHeader("Type", "MATCHING");
        //
        OnMessage(header, Buffer());
    }
    void Session::OnMatched(const std::string& peer_token)
    {
        if (false == is_running_) {
            return ;
        }
        session_state_ = state::SESS_MATCHED;
        // 
        last_peer_token_ = peer_token;
        //
        HttpRequestHeaderPtr header = HttpRequestHeader::Create();
        header->SetMethod("EVENT");
        header->SetPath("/status");
        header->SetProtocol("XW");
        header->SetVersion("1.0");
        header->SetHeader("Type", "MATCHED");
        //
        OnMessage(header, Buffer());
    }
    void Session::OnDisconnect()
    {
        if (false == is_running_) {
            return ;
        }
        session_state_ = state::SESS_NULL;
        //
        //
        HttpRequestHeaderPtr header = HttpRequestHeader::Create();
        header->SetMethod("EVENT");
        header->SetPath("/disconnect");
        header->SetProtocol("XW");
        header->SetVersion("1.0");
        //
        OnMessage(header, Buffer());
    }
    void Session::OnStatistic()
    {
        if (false == is_running_) {
            return ;
        }
        HttpRequestHeaderPtr header = HttpRequestHeader::Create();
        header->SetMethod("EVENT");
        header->SetPath("/statistic");
        header->SetProtocol("XW");
        header->SetVersion("1.0");
        //
        OnMessage(header, Buffer());
    }

}